//! Validation module
//! 
//! This module implements the assertion and validation system,
//! corresponding to HttpRunner's validation capabilities.

pub mod validators;
pub mod assertions;
pub mod extractors;

pub use validators::*;
pub use assertions::*;
pub use extractors::*;

use crate::error::{Result, Error, ValidationError};
use crate::models::{Value, ValueMap};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Validation result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResult {
    /// Whether validation passed
    pub success: bool,
    
    /// Validation message
    pub message: String,
    
    /// Expected value
    pub expected: Option<Value>,
    
    /// Actual value
    pub actual: Option<Value>,
    
    /// Validator type
    pub validator_type: String,
}

impl ValidationResult {
    /// Create a successful validation result
    pub fn success(validator_type: &str, message: &str) -> Self {
        Self {
            success: true,
            message: message.to_string(),
            expected: None,
            actual: None,
            validator_type: validator_type.to_string(),
        }
    }
    
    /// Create a failed validation result
    pub fn failure(
        validator_type: &str,
        message: &str,
        expected: Option<Value>,
        actual: Option<Value>,
    ) -> Self {
        Self {
            success: false,
            message: message.to_string(),
            expected,
            actual,
            validator_type: validator_type.to_string(),
        }
    }
}

/// Validation context containing response data and variables
#[derive(Debug, Clone)]
pub struct ValidationContext {
    /// Response data
    pub response: ValueMap,
    
    /// Session variables
    pub variables: ValueMap,
    
    /// Extracted variables from current step
    pub extracted_vars: ValueMap,
}

impl ValidationContext {
    /// Create new validation context
    pub fn new(response: ValueMap, variables: ValueMap) -> Self {
        Self {
            response,
            variables,
            extracted_vars: HashMap::new(),
        }
    }
    
    /// Get value from response
    pub fn get_response_value(&self, path: &str) -> Option<&Value> {
        self.get_nested_value(&self.response, path)
    }
    
    /// Get variable value
    pub fn get_variable(&self, name: &str) -> Option<&Value> {
        self.variables.get(name)
    }
    
    /// Get extracted variable
    pub fn get_extracted_var(&self, name: &str) -> Option<&Value> {
        self.extracted_vars.get(name)
    }
    
    /// Get nested value using dot notation (e.g., "response.body.data.id")
    fn get_nested_value<'a>(&self, data: &'a ValueMap, path: &str) -> Option<&'a Value> {
        let parts: Vec<&str> = path.split('.').collect();

        if parts.is_empty() {
            return None;
        }

        if parts.len() == 1 {
            return data.get(parts[0]);
        }

        let mut current_value = data.get(parts[0])?;

        for part in &parts[1..] {
            if let Some(obj) = current_value.as_object() {
                current_value = obj.get(*part)?;
            } else {
                return None;
            }
        }

        Some(current_value)
    }
}

/// Validator trait for implementing different validation types
pub trait Validator: Send + Sync {
    /// Validate a value against the expected criteria
    fn validate(&self, context: &ValidationContext) -> Result<ValidationResult>;
    
    /// Get validator name
    fn name(&self) -> &str;
    
    /// Get validator description
    fn description(&self) -> &str {
        self.name()
    }
}

/// Extractor trait for extracting values from responses
pub trait Extractor: Send + Sync {
    /// Extract value from response
    fn extract(&self, context: &ValidationContext) -> Result<Option<Value>>;
    
    /// Get extractor name
    fn name(&self) -> &str;
    
    /// Get variable name to store extracted value
    fn variable_name(&self) -> &str;
}

/// Validation engine that manages validators and extractors
pub struct ValidationEngine {
    /// Registered validators
    validators: HashMap<String, Box<dyn Validator>>,

    /// Registered extractors
    extractors: HashMap<String, Box<dyn Extractor>>,
}

impl ValidationEngine {
    /// Create new validation engine
    pub fn new() -> Self {
        let mut engine = Self {
            validators: HashMap::new(),
            extractors: HashMap::new(),
        };
        
        // Register built-in validators
        engine.register_builtin_validators();
        
        engine
    }
    
    /// Register a validator
    pub fn register_validator(&mut self, name: String, validator: Box<dyn Validator>) {
        self.validators.insert(name, validator);
    }
    
    /// Register an extractor
    pub fn register_extractor(&mut self, name: String, extractor: Box<dyn Extractor>) {
        self.extractors.insert(name, extractor);
    }
    
    /// Validate response using specified validators
    pub fn validate(&self, validators: &ValueMap, context: &ValidationContext) -> Result<Vec<ValidationResult>> {
        let mut results = Vec::new();
        
        for (validator_name, validator_config) in validators {
            if let Some(validator) = self.validators.get(validator_name) {
                let result = validator.validate(context)?;
                results.push(result);
            } else {
                results.push(ValidationResult::failure(
                    validator_name,
                    &format!("Unknown validator: {}", validator_name),
                    None,
                    None,
                ));
            }
        }
        
        Ok(results)
    }
    
    /// Extract variables from response
    pub fn extract(&self, extractors: &ValueMap, context: &mut ValidationContext) -> Result<ValueMap> {
        let mut extracted = HashMap::new();
        
        for (extractor_name, extractor_config) in extractors {
            if let Some(extractor) = self.extractors.get(extractor_name) {
                if let Some(value) = extractor.extract(context)? {
                    extracted.insert(extractor.variable_name().to_string(), value);
                }
            }
        }
        
        // Update context with extracted variables
        context.extracted_vars.extend(extracted.clone());
        
        Ok(extracted)
    }
    
    /// Register built-in validators
    fn register_builtin_validators(&mut self) {
        // Register common validators
        self.register_validator("status_code".to_string(), Box::new(validators::StatusCodeValidator::new(200)));
        self.register_validator("equals".to_string(), Box::new(validators::EqualsValidator::new("test".to_string(), serde_json::json!("test"))));
        self.register_validator("contains".to_string(), Box::new(validators::ContainsValidator::new("test".to_string(), serde_json::json!("test"))));
        self.register_validator("length".to_string(), Box::new(validators::LengthValidator::new("test".to_string(), 0)));
        self.register_validator("type".to_string(), Box::new(validators::TypeValidator::new("test".to_string(), "string".to_string())));

        // Register common extractors
        self.register_extractor("json_path".to_string(), Box::new(extractors::JsonPathExtractor::new("test".to_string(), "test".to_string())));
        self.register_extractor("header".to_string(), Box::new(extractors::HeaderExtractor::new("test".to_string(), "test".to_string())));
        self.register_extractor("cookie".to_string(), Box::new(extractors::CookieExtractor::new("test".to_string(), "test".to_string())));
        self.register_extractor("status_code".to_string(), Box::new(extractors::StatusCodeExtractor::new("test".to_string())));
        self.register_extractor("response_time".to_string(), Box::new(extractors::ResponseTimeExtractor::new("test".to_string())));
        self.register_extractor("content_length".to_string(), Box::new(extractors::ContentLengthExtractor::new("test".to_string())));
    }
}

impl Default for ValidationEngine {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_validation_context() {
        let response = HashMap::from([
            ("status_code".to_string(), json!(200)),
            ("body".to_string(), json!({"message": "success", "data": {"id": 123}})),
        ]);
        
        let variables = HashMap::from([
            ("base_url".to_string(), json!("https://api.example.com")),
        ]);
        
        let context = ValidationContext::new(response, variables);
        
        // Test response value access
        assert_eq!(context.get_response_value("status_code"), Some(&json!(200)));
        assert_eq!(context.get_response_value("body.message"), Some(&json!("success")));
        assert_eq!(context.get_response_value("body.data.id"), Some(&json!(123)));
        
        // Test variable access
        assert_eq!(context.get_variable("base_url"), Some(&json!("https://api.example.com")));
        
        // Test non-existent paths
        assert_eq!(context.get_response_value("nonexistent"), None);
        assert_eq!(context.get_response_value("body.nonexistent"), None);
    }

    #[test]
    fn test_validation_result() {
        let success = ValidationResult::success("status_code", "Status code is 200");
        assert!(success.success);
        assert_eq!(success.validator_type, "status_code");
        
        let failure = ValidationResult::failure(
            "status_code",
            "Expected 200, got 404",
            Some(json!(200)),
            Some(json!(404)),
        );
        assert!(!failure.success);
        assert_eq!(failure.expected, Some(json!(200)));
        assert_eq!(failure.actual, Some(json!(404)));
    }
}

impl std::fmt::Debug for ValidationEngine {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ValidationEngine")
            .field("validators", &format!("{} validators", self.validators.len()))
            .field("extractors", &format!("{} extractors", self.extractors.len()))
            .finish()
    }
}
